package com.nulldev.util.networking.async2.tcp.listeners.packets;

import java.io.IOException;
import java.util.concurrent.RejectedExecutionException;

import com.nulldev.util.networking.async2.events.ManualResetEvent;
import com.nulldev.util.networking.async2.tcp.listeners.TCPClientConnection;
import com.nulldev.util.networking.async2.tcp.listeners.exceptions.OutgoingPacketFailedException;

public final class OutgoingTCPClientConnectionPacket implements Runnable {

	public final ManualResetEvent fDone = new ManualResetEvent(false);
	private final TCPClientConnection fClient;
	private final byte[] fBytes;
	private final int fOffset;
	private final int fLength;
	private OutgoingTCPClientConnectionPacket fPreviousPacket;
	public boolean fCancel = false;

	/**
	 * Constructs a new outgoing packet for an existing TCP client connection.
	 *
	 * Use this class to send data from the server to a connected client. This is an
	 * outgoing message that will remain in the servers thread pool as a job for the
	 * thread pool workers.
	 *
	 * @param client         is the client where this message belongs to.
	 * @param bytes          is the byte array to be sent.
	 * @param offset         is the position in the data buffer at witch to begin
	 *                       sending.
	 * @param length         is the number of the bytes to be send.
	 * @param previousPacket is the previous outgoing packet of the client.
	 * @throws Extasys.Network.TCP.Server.Listener.Exceptions.OutgoingPacketFailedException
	 */
	public OutgoingTCPClientConnectionPacket(TCPClientConnection client, byte[] bytes, int offset, int length, OutgoingTCPClientConnectionPacket previousPacket)
			throws OutgoingPacketFailedException {
		fClient = client;
		fBytes = bytes;
		fOffset = offset;
		fLength = length;
		fPreviousPacket = previousPacket;

		sendToThreadPool();
	}

	protected void sendToThreadPool() {
		try {
			fClient.fServer.fMyThreadPool.execute(this);
		} catch (RejectedExecutionException ex) {
			fClient.forceDisconnect();
		}
	}

	/**
	 * Cancel this outgoing packet.
	 *
	 * By calling this method this and all the previous outgoing packets that are
	 * stored in the thread pool will be canceled. Call this method for the last
	 * outgoing packet of the client when the client disconnects.
	 *
	 */
	public void cancel() {
		fCancel = true;
		fDone.set();
	}

	@Override
	public void run() {
		try {
			if (fPreviousPacket == null) {
				try {
					fClient.fOutput.write(fBytes, fOffset, fLength);
					fClient.fOutput.flush();
					fClient.fBytesOut += fLength;
					fClient.fMyListener.fBytesOut += fLength;
				} catch (IOException ioException) {
					fCancel = true;
					fDone.set();
					fClient.disconnectMe();
				}
			} else {
				fPreviousPacket.fDone.waitOne();

				if (!fCancel && !fPreviousPacket.fCancel) {
					try {
						fClient.fOutput.write(fBytes, fOffset, fLength);
						fClient.fOutput.flush();
						fClient.fBytesOut += fLength;
						fClient.fMyListener.fBytesOut += fLength;
					} catch (IOException ioException) {
						fCancel = true;
						fDone.set();
						fClient.disconnectMe();
					}
				} else {
					fCancel = true;
				}

				fPreviousPacket = null;
			}
		} catch (Exception ex) {
		}

		fDone.set();

	}

	/**
	 * Returns the byte array of this packet.
	 *
	 * @return the byte array of this packet.
	 */
	public byte[] getBytes() {
		return fBytes;
	}

	/**
	 * Returns the offset of this packet.
	 *
	 * @return the offset of this packet.
	 */
	public int getOffset() {
		return fOffset;
	}

	/**
	 * Returns the number of bytes to send from this packet.
	 *
	 * @return the number of bytes to send from this packet.
	 */
	public int getLength() {
		return fLength;
	}
}